﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace QQ2564874169.Core.ORM
{
    public interface IDbExecute : IDbCore
    {
        event EventHandler<DbExecuteBeforeEventArgs> Before;
        event EventHandler<DbExecuteAfterEventArgs> After;
        event EventHandler<DbExecuteErrorEventArgs> Error;

        int Insert<T>(T model, int? timeout = null);
        int Delete<T>(T where, int? timeout = null);
        int Update<T>(T where, T sets, int? timeout = null);
        int SetNull<T>(T where, ChoiceField<T> sets, int? timeout = null);
        int Proc<T>(T model, int? timeout = null);
        IDbEntityQuery Query { get; }
    }

    public abstract class DbExecute : IDbExecute
    {
        public static event Action<DbExecute> Created;
        public event EventHandler<DbExecuteBeforeEventArgs> Before;
        public event EventHandler<DbExecuteAfterEventArgs> After;
        public event EventHandler<DbExecuteErrorEventArgs> Error;
        public event EventHandler<TransactionFinishEventArgs> TransactionBefore
        {
            add => _sql.TransactionBefore += value;
            remove => _sql.TransactionBefore -= value;
        }
        public event EventHandler<TransactionFinishEventArgs> TransactionAfter
        {
            add => _sql.TransactionAfter += value;
            remove => _sql.TransactionAfter -= value;
        }
        public Hashtable Data { get; }
        public bool InTransaction => _sql.InTransaction;

        private ISql _sql;
        public IDbEntityQuery Query { get; }

        protected DbExecute(ISql sql)
        {
            _sql = sql;
            Data = new Hashtable();
            Query = GetEntityQuery(sql);
            Created?.Invoke(this);
        }

        public virtual void Dispose()
        {
            Query.Dispose();
            Data.Clear();
        }

        protected virtual void OnBefore(DbExecuteBeforeEventArgs e)
        {
            Before?.Invoke(this, e);
        }

        protected virtual void OnAfter(DbExecuteAfterEventArgs e)
        {
            After?.Invoke(this, e);
        }

        protected virtual void OnError(DbExecuteErrorEventArgs e)
        {
            Error?.Invoke(this, e);
        }

        protected abstract IDbEntityQuery GetEntityQuery(ISql sql);

        protected virtual ExecuteParam OnInsertSql(EntityParam context)
        {
            if (context.Paramters.Count < 1)
                throw new ArgumentException("解析SQL失败，没有可新增的内容。");
            var left = string.Join(",", context.Paramters.Keys.Select(i => $"[{i}]"));
            var right = string.Join(",", context.Paramters.Keys.Select(i => $"@{i}"));

            var param = new ExecuteParam
            {
                Command = $"INSERT INTO [{context.SqlName}]({left}) VALUES({right})",
            };
            param.Parameters.Add(context.Paramters);
            return param;
        }

        protected virtual ExecuteParam OnDeleteSql(EntityParam context)
        {
            var where = string.Join(" AND ", context.Paramters.Keys.Select(k => $"[{k}]=@{k}"));
            if (context.Paramters.Count > 0)
                where = " WHERE " + where;
            var param = new ExecuteParam
            {
                Command = $"DELETE FROM [{context.SqlName}]{where}"
            };
            param.Parameters.Add(context.Paramters);
            return param;
        }

        protected virtual ExecuteParam OnUpdateSql(EntityParam sets, EntityParam where)
        {
            if (sets.Paramters.Count < 1)
                throw new ArgumentException("没有可更新的字段");

            var map = new Dictionary<string, object>();
            var setsql = string.Join(",", sets.Paramters.Keys.Select(k => $"[{k}]=@s_{k}"));
            foreach (var key in sets.Paramters.Keys)
            {
                map.Add("s_" + key, sets.Paramters[key]);
            }

            var wheresql = string.Join(" AND ", where.Paramters.Keys.Select(k => $"[{k}]=@w_{k}"));
            if (where.Paramters.Count > 0)
            {
                wheresql = " WHERE " + wheresql;
                foreach (var key in where.Paramters.Keys)
                {
                    map.Add("w_" + key, where.Paramters[key]);
                }
            }
            var param = new ExecuteParam
            {
                Command = $"UPDATE [{where.SqlName}] SET {setsql}{wheresql}"
            };
            param.Parameters.Add(map);
            return param;
        }

        protected virtual ExecuteParam OnSetNullSql(string[] sets, EntityParam where)
        {
            if (sets.Length < 1)
                throw new ArgumentException("没有可更新的字段");

            var setsql = string.Join(",", sets.Select(k => $"[{k}]=NULL"));
            var wheresql = string.Join(" AND ", where.Paramters.Keys.Select(k => $"[{k}]=@{k}"));
            if (where.Paramters.Count > 0)
            {
                wheresql = " WHERE " + wheresql;
            }
            var param = new ExecuteParam
            {
                Command = $"UPDATE [{where.SqlName}] SET {setsql}{wheresql}"
            };
            param.Parameters.Add(where.Paramters);
            return param;
        }

        protected virtual ExecuteParam OnProcSql(EntityParam context)
        {
            var param = new ExecuteParam
            {
                Command = context.SqlName,
                IsProc = true
            };
            param.Parameters.Add(context.Paramters);
            return param;
        }

        private int OnExecute(DbExecuteBeforeEventArgs before, int? timeout = null)
        {
            try
            {
                OnBefore(before);
                if (before.Result.HasValue == false)
                {
                    ExecuteParam param;
                    switch (before.Operation)
                    {
                        case DbExecuteOperation.Insert:
                            param = OnInsertSql(EntityParam.Parse(before.TableType, before.Model));
                            break;
                        case DbExecuteOperation.Delete:
                            param = OnDeleteSql(EntityParam.Parse(before.TableType, before.Model));
                            break;
                        case DbExecuteOperation.Proc:
                            param = OnProcSql(EntityParam.Parse(before.TableType, before.Model));
                            break;
                        case DbExecuteOperation.SetNull:
                            if (before.Model is DbSetNullEventArgs setArgs)
                            {
                                var where = EntityParam.Parse(before.TableType, setArgs.Where);
                                param = OnSetNullSql(setArgs.Sets, where);
                            }
                            else
                            {
                                throw new DbExecuteException("Model不是DbUpdateEventArgs类型，无法解析。");
                            }
                            break;
                        case DbExecuteOperation.Update:
                            if (before.Model is DbUpdateEventArgs upArgs)
                            {
                                var sets = EntityParam.Parse(before.TableType, upArgs.Sets);
                                var where = EntityParam.Parse(before.TableType, upArgs.Where);
                                param = OnUpdateSql(sets, where);
                            }
                            else
                            {
                                throw new DbExecuteException("Model不是DbUpdateEventArgs类型，无法解析。");
                            }

                            break;
                        default:
                            throw new NotSupportedException();

                    }

                    before.Result = before.Sql.Execute(param.Command, param.Parameters, param.IsProc, timeout);
                }
                return before.Result.Value;
            }
            catch (Exception ex)
            {
                var e = new DbExecuteErrorEventArgs(before, ex);
                OnError(e);
                if (e.Result.HasValue)
                {
                    return e.Result.Value;
                }
                throw;
            }
            finally
            {
                OnAfter(new DbExecuteAfterEventArgs(before));
            }
        }

        public int Insert<T>(T model, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.Insert,
                TableType = typeof(T),
                Model = model
            };
            return OnExecute(before, timeout);
        }

        public int Delete<T>(T model, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.Delete,
                TableType = typeof(T),
                Model = model
            };
            return OnExecute(before, timeout);
        }

        public int Update<T>(T sets, T where, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.Update,
                TableType = typeof(T),
                Model = new DbUpdateEventArgs(where, sets)
            };
            return OnExecute(before, timeout);
        }

        public int SetNull<T>(T where, ChoiceField<T> sets, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.SetNull,
                TableType = typeof(T),
                Model = new DbSetNullEventArgs(where, sets.GetFields())
            };
            return OnExecute(before, timeout);
        }

        public int Proc<T>(T model, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.Proc,
                TableType = typeof(T),
                Model = model
            };
            return OnExecute(before, timeout);
        }

        public ITransactionScope Transaction()
        {
            return _sql.Transaction();
        }
    }
}
